Skip to content
lab components / Actions and controls

Site picker

Allow users to select a site.

This is a Lab component!

That means it doesn't satisfy our definition of done and may be changed or even deleted. For an exact status, please reach out to the Fancy team through the dev_fancy or ux_fancy channels.

import { SitePicker } from "@siteimprove/fancylab";

#Examples

Default: Button shows selected site or an empty placeholder text if no site is selected.

The site picker consists of two main elements:

  • Button (with icon) to display the selected site.
  • Popover (triggered by button click) with a table of sites and default columns with data related to the site.
type SitesRequest = { query: string; page: number; pageSize: number; sortField: SortField<Site> }; const [request, setRequest] = useState<SitesRequest>({ query: "", page: 1, pageSize: 20, sortField: { property: "isFavorite", direction: "asc" }, }); const { api } = useDataAPI(); const fetchDataFn = async (request: SitesRequest, signal?: AbortSignal) => await api.getSites(request, signal); const { data, loading, triggerRender } = useData(fetchDataFn, request); const [selected, setSelected] = useState<Site>(); return ( <PageHeaderPickers items={ data && ( <SitePicker items={data.items} sort={request.sortField} setSort={(property, direction) => setRequest({ ...request, sortField: { property, direction: property === request.sortField.property ? invertDirection(request.sortField.direction) : direction, }, }) } search={{ query: request.query, onSearch: (query) => setRequest({ ...request, query }), }} onLoadMore={() => setRequest((prev) => ({ ...prev, pageSize: prev.pageSize + 20 }))} totalItems={data.totalItems} selectedSite={selected} onSelectedSite={setSelected} onFavorite={async (site, isFavorite) => { await api.updateFavoriteSite(site, isFavorite); triggerRender(); }} editSitesUrl="https://fancy.siteimprove.com/" loading={loading} /> ) } /> );

#Usage with no sites

Used when data is unavailable.

return ( <PageHeaderPickers items={ <SitePicker items={[]} sort={{ property: "isFavorite", direction: "asc" }} setSort={() => {}} search={{ query: "", onSearch: () => {}, }} onLoadMore={() => {}} onSelectedSite={() => {}} onFavorite={() => {}} totalItems={0} editSitesUrl="https://fancy.siteimprove.com/" /> } /> );

#Usage with many sites

The load more button will load more sites.

type SitesRequest = { query: string; page: number; pageSize: number; sortField: SortField<Site> }; const [request, setRequest] = useState<SitesRequest>({ query: "", page: 1, pageSize: 20, sortField: { property: "isFavorite", direction: "asc" }, }); const { api } = useDataAPI(); const fetchDataFn = async (request: SitesRequest, signal?: AbortSignal) => { await new Promise((resolve) => setTimeout(resolve, 2000)); return await api.getSitesLarge(request, signal); }; const { data, loading, triggerRender } = useData(fetchDataFn, request); const [selected, setSelected] = useState<Site>(); const pickersLoading = loading && data === null; return ( <PageHeaderPickers loading={pickersLoading} items={ data && ( <SitePicker items={data.items} loading={loading} sort={request.sortField} setSort={(property, direction) => setRequest((prev) => ({ ...prev, sortField: { property, direction: property === request.sortField.property ? invertDirection(request.sortField.direction) : direction, }, })) } search={{ query: request.query, onSearch: (query) => setRequest({ ...request, query }), }} onLoadMore={() => setRequest((prev) => ({ ...prev, pageSize: prev.pageSize + 20 }))} totalItems={data.totalItems} selectedSite={selected} onSelectedSite={setSelected} onFavorite={async (site, isFavorite) => { await api.updateFavoriteSiteLarge(site, isFavorite); triggerRender(); }} editSitesUrl="https://fancy.siteimprove.com/" extraColumns={[colPages(), colVisits()]} /> ) } /> );

#Properties

PropertyDescriptionDefinedValue
itemsRequired
object[]Items displayed in the site picker.
totalItemsRequired
numberTotal amount of sites.
sortRequired
objectProperty and direction by which the sites table is sorted.
setSortRequired
functionCallback for updating sorting.
objectSearch options to filter sites.
onLoadMoreRequired
functionLoad more items callback
editSitesUrlRequired
stringURL to edit sites page
onSelectedSiteRequired
functionCallback when a site is selected
onFavoriteRequired
functionCallback to favorite/unfavorite a site
selectedSiteOptional
objectSelected site item
loadingOptional
booleanIf the site picker is loading
extraColumnsOptional
type-union[]Extra columns to be displayed in the site picker additional to the default columns
textsOptional
objectTexts for the site picker

#Guidelines

#Do's

  • Focus on the most important actions for the current context.
  • Strictly adhere to Siteimprove's brand guidelines for colors, typography, and iconography.
  • Use ample white space to maintain a clean and uncluttered appearance.
  • Ensure a balanced distribution of elements throughout the header.
  • Reveal additional options or actions as needed, rather than displaying everything at once.

#Don'ts

  • Avoid overloading the header with too many features, which can distract users from core tasks.
  • Reserve alerts for truly critical, time-sensitive messages requiring immediate user attention.
  • Prioritize essential actions and consider moving less-used ones to dropdown menus or other locations.
  • Avoid hiding crucial actions behind unclear icons or gestures.

#Accessibility

For developers, ensure the header is accessible by adhering to the following best practices:
  • Breadcrumbs: Placed inside the <main> element. Make the breadcrumb container a <nav aria-label="Breadcrumbs">...</a>
  • Heading: Placed inside the <main> element. Make the heading an <h1> element.
  • Main content section: Use the <main> element for this (Note: A page must only have one <main> element). It must have id="content" and tabindex="-1" for the page's="Skip to main content" link to work.
  • Page toolbar: Placed inside the <main> element, This container should have role="group" and aria-label="Page toolbar"
  • Page filter: Placed inside the <main> element. This container should have role="group" and aria-label="Page filter"

Explore detailed guidelines for this component: Accessibility Specifications

#Writing

Page Titles:

  • Use the same label from the side navigation for consistency.
  • Keep them short, informative, and in sentence case (e.g., "Account Settings").

Button Labels:

  • Employ action verbs (e.g., "Create," "Edit," "Delete").
  • Consult the Word list for approved terminology.

Alert Messages:

  • Prioritize clarity and conciseness.
  • Focus on the essential information users need to know.